#include "Pch.h"
#include "ResidueWindow.h"
#include "MainWindow.h"


class ResidueList : public wxListCtrl {
	//PDBManager *pdbManager;

protected:
	// Doesn't work.  wxLC_VIRTUAL is apparently broken.
	/*
	wxListItemAttr *OnGetItemAttr (long item) const {
		static wxListItemAttr attr;
		wxColour text, bg;
		LoadedChain *chain = &pdbManager->chains[pdbManager->activeChain];
		if (!(chain->residueInfo[item].flags & RESIDUE_SELECTED)) {
			attr.SetBackgroundColour(bg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
			attr.SetTextColour(text = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
		}
		else {
			return 0;
			// Doesn't work under windows, unfortunately, as highlighting uses default colors,
			// even when using custom draw.  Workaround would involved modifying wxWidgets.
			attr.SetBackgroundColour(bg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
			attr.SetTextColour(text = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
		}

		Color c = chain->GetResidueColor(item);
		int dtext = abs(c.r - text.Red()) + abs(c.g - text.Green()) + abs(c.b - text.Blue());
		int dbg = abs(c.r - bg.Red()) + abs(c.g - bg.Green()) + abs(c.b - bg.Blue());
		if (dtext < dbg) {
			attr.SetTextColour(wxColour(c.r, c.g, c.b));
		}
		else {
			attr.SetBackgroundColour(wxColour(c.r, c.g, c.b));
		}
		return &attr;
	}
	
	wxString OnGetItemText (long item, long column) const {
		LoadedChain *chain = &pdbManager->chains[pdbManager->activeChain];
		Residue *res = &chain->chain->residues[item];
		if (column == 0) {
			return wxString::Format(_T("%i%c"), res->resNum, res->resLetter);
		}
		else if (column == 1) {
			return wxString(res->resName, wxConvUTF8);
		}
		return wxEmptyString;
	}//*/

	void OnEraseBackground(wxEraseEvent & evt);
public:
	ResidueList(/* PDBManager *pdbManager, */ wxWindow *parent) :
		wxListCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT)
	{
		//this->pdbManager = pdbManager;
	}

	DECLARE_EVENT_TABLE()
};

void ResidueList::OnEraseBackground(wxEraseEvent & evt) {
   // to prevent flickering, erase only content *outside* of the 
   // actual list items stuff
   if(GetItemCount() > 0) {
       wxDC * dc = evt.GetDC();

       // get some info
       wxCoord width = 0, height = 0;
       GetClientSize(&width, &height);

       wxCoord x, y, w, h;
       dc->SetClippingRegion(0, 0, width, height);
       dc->GetClippingBox(&x, &y, &w, &h); 

       long top_item = GetTopItem();
       long bottom_item = top_item + GetCountPerPage();
       if(bottom_item >= GetItemCount()) {
           bottom_item = GetItemCount() - 1;
       }                

       // trick: we want to exclude a couple pixels
       // on the left side thus use wxLIST_RECT_LABEL
       // for the top rect and wxLIST_RECT_BOUNDS for bottom
       // rect
       wxRect top_rect, bottom_rect;
       GetItemRect(top_item, top_rect, wxLIST_RECT_LABEL);
       GetItemRect(bottom_item, bottom_rect, wxLIST_RECT_BOUNDS);

       // set the new clipping region and do erasing
       wxRect items_rect(top_rect.GetLeftTop(), bottom_rect.GetBottomRight());
       wxRegion reg(wxRegion(x, y, w, h)); 
       reg.Subtract(items_rect);
       dc->DestroyClippingRegion();
       dc->SetClippingRegion(reg);

       // do erasing
       dc->SetBackground(wxBrush(GetBackgroundColour(), wxSOLID));
       dc->Clear();

       // restore old clipping region
       dc->DestroyClippingRegion();
       dc->SetClippingRegion(wxRegion(x, y, w, h));
   } else {
       evt.Skip();
   }
}

BEGIN_EVENT_TABLE(ResidueList, wxListCtrl)
	EVT_ERASE_BACKGROUND(ResidueList::OnEraseBackground)
END_EVENT_TABLE()

BEGIN_EVENT_TABLE(ResidueWindow, wxPanel)
	EVT_SIZE(ResidueWindow::OnSize)
	EVT_LIST_ITEM_SELECTED(wxID_ANY, ResidueWindow::Selected)
	EVT_LIST_ITEM_DESELECTED(wxID_ANY, ResidueWindow::UnSelected)
	EVT_COMBOBOX(wxID_ANY, ResidueWindow::ChainSelected)
	EVT_MENU(wxID_SELECTALL, ResidueWindow::OnSelectAll)
END_EVENT_TABLE()

void ResidueWindow::OnSelectAll(wxCommandEvent& evt) {
}

ResidueWindow::ResidueWindow(wxWindow *parent, MainWindow *mainWindow, PDBManager* pdbManager) :
wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxBORDER_NONE)
{
	chainLocked = 0;
	activeChain = 0;
	lastActiveChain = -1;
	this->mainWindow = mainWindow;
	this->pdbManager = pdbManager;

	combo = new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, 0, wxCB_READONLY);
	list = new ResidueList(/*pdbManager, */ this);
	//list = new wxListCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT);

	list->InsertColumn(0, _T("ID"), wxLIST_FORMAT_RIGHT, 40);

	// Windows hack:  Have to set leftmost column to right alignment after creating it.
	// Believe it's a Windows issue and not a wxWidgets one.
	wxListItem item;
	list->GetColumn(0, item);
	item.SetAlign(wxLIST_FORMAT_RIGHT);
	list->SetColumn(0, item);

	list->InsertColumn(1, _T("Res"), wxLIST_FORMAT_LEFT, 40);
	wxSizer *sizer = new wxBoxSizer(wxVERTICAL);

	sizer->Add(combo, 0, wxEXPAND);
	sizer->AddSpacer(4);
	sizer->Add(list, 1, wxEXPAND);

	SetSizer(sizer);
}

void ResidueWindow::ChainSelected(wxCommandEvent& evt) {
	int index = evt.GetSelection();
	if (index >= 0) {
		if (!chainLocked)
			activeChain = index;
		UpdateDisplay();
	}
}

// Was hoping this would let me notice all items being selected/unselected at once,
// but doesn't seem to work that way.
/*
void ResidueWindow::UpdateSelected() {
	LoadedChain *chain = &pdbManager->chains[pdbManager->activeChain];
	for (int i=0; i<chain->numResidues; i++) {
		if (list->GetItemState(i, wxLIST_STATE_SELECTED)) {
			if (!(chain->residueInfo[i].flags & (RESIDUE_SELECTED | RESIDUE_LAST_SELECTED))) {
				chain->residueInfo[i].flags |= RESIDUE_SELECTED;
			}
		}
		else {
			chain->residueInfo[i].flags &= ~(RESIDUE_SELECTED | RESIDUE_LAST_SELECTED);
		}
	}
}
//*/

void ResidueWindow::Selected(wxListEvent& evt) {
	int index = evt.GetIndex();
	ManagedChain *chain = pdbManager->chains[lastActiveChain];
	if (!(chain->residueInfo[index].flags & (RESIDUE_SELECTED | RESIDUE_LAST_SELECTED))) {
		//UpdateSelected();
		chain->residueInfo[index].flags |= RESIDUE_SELECTED;
		pdbManager->SaveUndoState();
		//pdbManager->Redraw(~REDRAW_RESIDUES, pdbManager->activeChain, index);
		pdbManager->Redraw(REDRAW_ALL & ~REDRAW_RESIDUES);
	}
}

void ResidueWindow::UnSelected(wxListEvent& evt) {
	int index = evt.GetIndex();
	ManagedChain *chain = pdbManager->chains[lastActiveChain];
	if (chain->residueInfo[index].flags & (RESIDUE_SELECTED | RESIDUE_LAST_SELECTED)) {
		//UpdateSelected();
		chain->residueInfo[index].flags &= ~(RESIDUE_SELECTED | RESIDUE_LAST_SELECTED);
		pdbManager->SaveUndoState();
		//pdbManager->Redraw(~REDRAW_RESIDUES, pdbManager->activeChain, index);
		pdbManager->Redraw(REDRAW_ALL & ~REDRAW_RESIDUES);
	}
}

ResidueWindow::~ResidueWindow() {}

void ResidueWindow::OnSize(wxSizeEvent &evt) {
	GetClientSize(&width, &height);
	wxPanel::OnSize(evt);
}

void ResidueWindow::UpdatePDB(PDBManager *manager, int forceChain) {
	pdbManager = manager;
	chainLocked = forceChain >= 0;
	combo->Enable(!chainLocked && manager->numChains > 1);
	if (chainLocked)
		activeChain = forceChain;
	else
		 activeChain = 0;
	lastActiveChain = -1;
	combo->Clear();
	if (pdbManager->numChains) {
		for (int i=0; i<pdbManager->numChains; i++) {
			if (chainLocked && i != activeChain)
				continue;
			combo->AppendString(wxString(pdbManager->chains[i]->idString, wxConvUTF8));
		}
		if (!chainLocked)
			combo->Select(activeChain);
		else
			combo->Select(0);
	}
	UpdateDisplay();
}

void ResidueWindow::UpdateResidueList() {
	if (pdbManager->numChains /* && pdbManager->activeChain >= 0 */) {
		list->DeleteAllItems();
		ManagedChain *chain = pdbManager->chains[activeChain];
		for (int i=0; i<chain->numChainResidues; i++) {
			PDBResidue *res = &chain->residues[i];
			if (res->resNum != PDB_ID_NONE)
				list->InsertItem(i, wxString::Format(_T("%i%c"), res->resNum, res->resLetter));
			else
				list->InsertItem(i, wxEmptyString);
			list->SetItem(i, 1, wxString(res->resName, wxConvUTF8));
		}
	}
	else {
		list->DeleteAllItems();
		//list->SetItemCount(0);
	}
	lastActiveChain = activeChain;
}

void ResidueWindow::UpdateDisplay(int jumpTo, int jumpToChain) {
	if (!pdbManager->numChains) {
		list->DeleteAllItems();
		return;
	}
	if (jumpToChain == -1 || (chainLocked && jumpToChain != activeChain)) {
		jumpTo = -1;
	}
	else {
		activeChain = jumpToChain;
	}

	int fullUpdate = 0;
	int lastChanged = -1;

	int refreshOnly = 0;
	if (jumpTo >= pdbManager->chains[activeChain]->numChainResidues) {
		if (lastActiveChain != activeChain) {
			if (pdbManager->chains[activeChain]->numChainResidues) {
				jumpTo = 0;
			}
			else {
				// TODO:  Figure out if I should do something when nothing to list.
				jumpTo = -1;
			}
		}
		else {
			jumpTo = -1;
		}
	}

	if (!refreshOnly) {
		if (lastActiveChain != activeChain) {
			list->Freeze();

			UpdateResidueList();
			combo->Select(lastActiveChain);
			fullUpdate = 1;
		}
	}

	wxColour listText = list->GetTextColour();
	wxColour listBg = list->GetBackgroundColour();
	ManagedChain *chain = pdbManager->chains[lastActiveChain];
	for (int i=0; i<chain->numChainResidues; i++) {
		PDBResidue *res = &chain->residues[i];
		if (chain->residueInfo[i].flags & (RESIDUE_SELECTED | RESIDUE_LAST_SELECTED)) {
			if (!list->GetItemState(i, wxLIST_STATE_SELECTED)) {
				if (!list->IsFrozen()) {
					list->Freeze();
				}
				list->SetItemState(i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
				lastChanged = i;
			}
		}
		else {
			if (list->GetItemState(i, wxLIST_STATE_SELECTED)) {
				if (!list->IsFrozen()) {
					list->Freeze();
				}
				list->SetItemState(i, 0, wxLIST_STATE_SELECTED);
				lastChanged = i;
			}
		}

		wxColour text = listText;
		wxColour bg = listBg;
		Color c = pdbManager->GetResidueColor(lastActiveChain, i, 1);
		int dtext = abs(c.r - text.Red()) + abs(c.g - text.Green()) + abs(c.b - text.Blue());
		int dbg = abs(c.r - bg.Red()) + abs(c.g - bg.Green()) + abs(c.b - bg.Blue());
		if (dtext < dbg) {
			text = WX_COLOR(c);
		}
		else {
			bg = WX_COLOR(c);
		}

		if (text != list->GetItemTextColour(i)) {
			if (!list->IsFrozen()) {
				list->Freeze();
			}
			list->SetItemTextColour(i, text);
			if (chain->residueInfo[i].flags & RESIDUE_LAST_SELECTED) {
				lastChanged = i;
			}
		}
		if (bg != list->GetItemBackgroundColour(i)) {
			if (!list->IsFrozen()) {
				list->Freeze();
			}
			list->SetItemBackgroundColour(i, bg);
			if (chain->residueInfo[i].flags & RESIDUE_LAST_SELECTED) {
				lastChanged = i;
			}
		}
	}
	if (jumpTo >= 0) {
		list->EnsureVisible(jumpTo);
	}
	else if (!fullUpdate && lastChanged >= 0) {
		list->EnsureVisible(lastChanged);
	}

	if (list->IsFrozen()) {
		list->Thaw();
	}
	/*
	wxListItemAttr *OnGetItemAttr (long item) const {
		static wxListItemAttr attr;
		LoadedChain *chain = &pdbManager->chains[pdbManager->activeChain];
		if (!(chain->residueInfo[item].flags & RESIDUE_SELECTED)) {
			attr.SetBackgroundColour(bg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
			attr.SetTextColour(text = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
		}
		else {
			return 0;
			// Doesn't work under windows, unfortunately, as highlighting uses default colors,
			// even when using custom draw.  Workaround would involved modifying wxWidgets.
			attr.SetBackgroundColour(bg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
			attr.SetTextColour(text = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
		}

		Color c = chain->GetResidueColor(item);
		int dtext = abs(c.r - text.Red()) + abs(c.g - text.Green()) + abs(c.b - text.Blue());
		int dbg = abs(c.r - bg.Red()) + abs(c.g - bg.Green()) + abs(c.b - bg.Blue());
		if (dtext < dbg) {
			attr.SetTextColour(wxColour(c.r, c.g, c.b));
		}
		else {
			attr.SetBackgroundColour(wxColour(c.r, c.g, c.b));
		}
		return &attr;
	}
	//*/
}

void ResidueWindow::ChangeSelection(int chain, int index) {
	if (chain != lastActiveChain) {
		UpdateDisplay(index, chain);
	}
}
